#include "BuildTool.h"

#include "HeadFile.h"
#include <fstream>
#include <iostream>

CBuildTool::CBuildTool()
{
}

CBuildTool::~CBuildTool()
{
}

void CBuildTool::Reflect(
    const std::vector<std::filesystem::path>& InDirectories
    ,const std::vector<std::u8string>& InNamespaces //每个目录对应一个命名空间
)
{   

    std::vector<std::filesystem::path> DeleteList;
    for(int i=0;i<InDirectories.size();i++)
    {
        auto& Directory=InDirectories[i];
        auto& Namespace=InNamespaces[i];

        //literal all .h and .cpp file
        for(auto& File: std::filesystem::recursive_directory_iterator(Directory))
        {
            if(!File.is_regular_file())
            {
                continue;
            }
            
            auto Path=File.path();
            auto Extension=Path.extension();
            if(Extension!=".h")
            {
                continue;
            }

            //get file name without extension
            auto FileName= Path.filename().replace_extension("");

            //skip special files
            if(FileName=="Reflection")
            {
                continue;
            }

            auto MExtension= FileName.extension();
            //if file name end with .m, it is a meta file
            bool IsMetaFile=MExtension==".m";

            if(IsMetaFile)
            {
                auto HeadFileName=FileName.replace_extension(".h");
                //get head file path
                auto HeadFilePath=Path.parent_path()/HeadFileName;

                //if Head file is not exist, add to delete list
                if(!std::filesystem::exists(HeadFilePath))
                {
                    DeleteList.push_back(Path);

                    //get .m.cpp file path
                    auto MCppFilePath=Path.replace_extension(".m.cpp");

                    DeleteList.push_back(MCppFilePath);
                }
            }
            else
            {
                auto HeadFile=std::make_shared<CHeadFile>();
                HeadFile->Path=Path;
                HeadFile->Namespace=Namespace;
                auto HeadPath=Path;
                HeadPath.replace_extension(".m.h");
                auto MCppPath=Path;
                MCppPath.replace_extension(".m.cpp");

                FileGroups.push_back({
                    HeadFile
                    ,HeadPath
                    ,MCppPath
                    });
            }
                
            
        }

        //delete file in delete list
        for(auto& Path: DeleteList)
        {
            std::filesystem::remove(Path);
        }

    }

    //generate class info
    std::map<std::u8string,std::shared_ptr<CClassInfo>> ClassMap; //only class, not struct
    std::vector<std::shared_ptr<CClassInfo>> ClassList; //all class and struct
    for(auto& FileGroup: FileGroups)
    {
        FileGroup.HeadFile->ParseFile();

        for(auto& Class : FileGroup.HeadFile->Classes)
        {
            if(!Class->IsClass())
            {
                continue;
            }

            if(ClassMap.contains(Class->Name))
            {
                FileGroup.HeadFile->RaiseFileError(u8"Error: class["+ Class->Name + u8"] is defined twice ");
            }
            ClassMap[Class->Name]=Class;
            ClassList.push_back(Class);
        }
    }

    //create RObject Info
    auto RObjectInfo=std::make_shared<CClassInfo>();
    {
        RObjectInfo->Name=u8"RObject";
        RObjectInfo->BaseClassName=u8"";
        RObjectInfo->BaseClass=nullptr;
        RObjectInfo->ClassType=EClassType::Class;
        RObjectInfo->ExportMacro=u8"WHENGINE_API";
        ClassMap[u8"RObject"]=RObjectInfo;
        ClassList.push_back(RObjectInfo);

        //add ToString() function
        {
            auto ToStringFunction=std::make_shared<CFunctionInfo>();
            ToStringFunction->Name=u8"ToString";
            ToStringFunction->ReturnType=u8"CString";
            ToStringFunction->IsStatic=false;
            ToStringFunction->IsVirtual=true;
            ToStringFunction->ParameterTypes={};
            RObjectInfo->Functions.push_back(ToStringFunction);
        }

    }

    //assign base class
    for(auto& Class: ClassList)
    {
        //skip RObject class
        if(Class==RObjectInfo)
        {
            continue;
        }

        if(Class->BaseClassName.empty())
        {
            RaiseError(u8"Error: class["+ Class->Name + u8"], parent class name is empty");
        }

        auto BaseClass=ClassMap[Class->BaseClassName];
        if(!BaseClass)
        {
            RaiseError(u8"Error: class["+ Class->Name + u8"]'s base class["+ Class->BaseClassName + u8"] is not defined");
        }
        Class->BaseClass=BaseClass;
    }

    //generate virtual function table
    {
        for(auto& Class: ClassList)
        {
            Class->GenerateVirtualFunctionTable();
        }
    }


    //generate meta file content
    for(auto& FileGroup: FileGroups)
    {
        FileGroup.HeadFile->GenerateMetaFileContent();

        //update meta file
        if( FileGroup.HeadFile->HeadFileContent.empty() )
        {
            //try delete meta file
            if(std::filesystem::exists(FileGroup.MetaFileHead))
            {
                std::filesystem::remove(FileGroup.MetaFileHead);
            }

            if(std::filesystem::exists(FileGroup.MetaFileCpp))
            {
                std::filesystem::remove(FileGroup.MetaFileCpp);
            }
        }
        else
        {
            //1. read old meta head file content
            if(std::filesystem::exists(FileGroup.MetaFileHead))
            {
                std::ifstream File(FileGroup.MetaFileHead);
                if (!File.is_open())
                {
                    FileGroup.HeadFile->RaiseFileError(u8"Cant open file: "+ FileGroup.MetaFileHead.u8string());
                }
                std::u8string OldMetaFileContent((std::istreambuf_iterator<char>(File)),
                        (std::istreambuf_iterator<char>()));

                //2. compare old meta file content and new meta file content
                if(OldMetaFileContent!=FileGroup.HeadFile->HeadFileContent)
                {
                    //3. if not equal, update meta file
                    File.close();
                    std::ofstream MetaFile(FileGroup.MetaFileHead);
                    MetaFile<<(const char*)FileGroup.HeadFile->HeadFileContent.c_str();
                }
            }
            else
            {
                //just create a new file and write content
                std::ofstream MetaFile(FileGroup.MetaFileHead);
                MetaFile<<(const char*)FileGroup.HeadFile->HeadFileContent.c_str();
            }

            //1. read old meta cpp file content
            if(std::filesystem::exists(FileGroup.MetaFileCpp))
            {
                std::ifstream File(FileGroup.MetaFileCpp);
                if (!File.is_open())
                {
                    FileGroup.HeadFile->RaiseFileError(u8"Cant open file: "+ FileGroup.MetaFileCpp.u8string());
                }
                std::u8string OldMetaFileContent((std::istreambuf_iterator<char>(File)),
                        (std::istreambuf_iterator<char>()));

                //2. compare old meta file content and new meta file content
                if(OldMetaFileContent!=FileGroup.HeadFile->CppFileContent)
                {
                    //3. if not equal, update meta file
                    File.close();
                    std::ofstream MetaFile(FileGroup.MetaFileCpp);
                    MetaFile<<(const char*)FileGroup.HeadFile->CppFileContent.c_str();
                }
            }
            else
            {
                //just create a new file and write content
                std::ofstream MetaFile(FileGroup.MetaFileCpp);
                MetaFile<<(const char*)FileGroup.HeadFile->CppFileContent.c_str();
            }
        }
    }

    

}

void CBuildTool::RaiseError(const std::u8string &InMessage)
{
    std::cout<<(const char *)InMessage.data()<<std::endl;
    throw std::runtime_error("Engine Build Tool Error");
}
